%{
#include "Sql_Node_Defs.hpp"
#include "lex_sql.hpp"
#include <iostream>

int yyerror(Sql_Node*& sql_node, const char* msg)
{
    std::cerr << msg << std::endl;
    return 0;
}

%}


%token CREATE
%token TABLE
%token LBRACE
%token RBRACE
%token COMMA
%token INT
%token CHAR
%token PRIMARY
%token KEY
%token SEMICOLON

%union {
    Sql_Node* sql_node; 
    Create_Table_Sql_Node* create_table_sql_node;
    std::vector<Attribute_Info>* attr_infos;
    Attribute_Info* attr_info;
    Attribute_Type  attr_type;
    int number;
    std::vector<std::string>* key_list;
    char* cstring;
}

/* 有语义值的终结符需这样定义 */
%token <number> NUMBER
%token <cstring> ID

/* 定义非终结符的语义值类型 */
/* <> 里面放的是union中的某个类型的名字 */
%type <sql_node> command
%type <sql_node> command_wrapper
%type <create_table_sql_node> create_table_stmt
%type <attr_infos> attr_def_list 
%type <attr_info> attr_def
%type <attr_type> type
%type <key_list> primary_key
%type <key_list> attr_list 

%parse-param { Sql_Node*& sql_node }

%%

command: command_wrapper opt_semicolon
    {
        $$ = $1;
        sql_node = static_cast<Sql_Node*>($$);
    }
    ;

command_wrapper: 
    create_table_stmt 
    {
        $$ = $1;
    }
    ;

create_table_stmt:
    CREATE TABLE ID LBRACE attr_def_list primary_key RBRACE
    {
        $$ = new Create_Table_Sql_Node(Sql_Command_Type::CREATE_TABLE);
        $$->relation_name = $3;
        $$->attr_infos.swap(*$5);
        delete $5;

        if ($6 != nullptr)
        {
            $$->primary_keys.swap(*$6);
            delete $6;
        }
    }
    ;

attr_def_list:
    attr_def 
    {
        $$ = new std::vector<Attribute_Info>;
        $$->emplace_back(*$1);
        delete $1;
    }
    | attr_def_list COMMA attr_def
    {
        $$ = $1;
        $$->emplace_back(*$3);
        delete $3;
    }
    ;
    
attr_def:
    ID type 
    {
        $$ = new Attribute_Info;
        $$->attr_name = $1;
        $$->attr_type = $2;
        $$->length = 4;
    }
    |
    ID type LBRACE NUMBER RBRACE
    {
        $$ = new Attribute_Info;
        $$->attr_name = $1;
        $$->attr_type = $2;
        $$->length = $4;
    }
    ;

type:
    INT { $$ = Attribute_Type::INT; }
    | CHAR { $$ = Attribute_Type::CHAR; }
    ;

primary_key:
    /* empty */ {}
    | COMMA PRIMARY KEY LBRACE attr_list RBRACE
    {
        $$ = $5;
    }
    ;

attr_list:
    ID 
    {
        $$ = new std::vector<std::string>;
        $$->emplace_back($1);
    }
    | attr_list COMMA ID 
    {
        $$ = $1;
        $$->emplace_back($3);
    }
    ;

opt_semicolon: /* empty */
    | SEMICOLON
    ;
%%

int main(int argc, char** argv)
{
    if (argc < 2)
    {
        std::cerr << "Usage: sql_parser <sql>\n";
        return 1;
    }
    int ret = 0;
    Sql_Node* sql_node = nullptr;
    YY_BUFFER_STATE buffer = yy_scan_string(argv[1]);
    ret = yyparse(sql_node);
    yy_delete_buffer(buffer);
    if (sql_node != nullptr && sql_node->command_type == Sql_Command_Type::CREATE_TABLE)
    {
        auto create_table_node = dynamic_cast<Create_Table_Sql_Node*>(sql_node);
        std::cout << "Create Table Command:" << std::endl;
        std::cout << "table name: " << create_table_node->relation_name << std::endl;
        for (int i = 0; i < create_table_node->attr_infos.size(); ++i)
        {
            auto& attr_info = create_table_node->attr_infos[i];
            const char* type_str = nullptr;
            if (attr_info.attr_type == Attribute_Type::INT)
                type_str = "INT";
            else if (attr_info.attr_type == Attribute_Type::CHAR)
                type_str = "CHAR";
            else
                type_str = "UNKNOWN";
            std::cout << "attribute: name=" << attr_info.attr_name
                      << ",type=" << type_str 
                      << ",length=" << attr_info.length << std::endl;
        }

        std::cout << "primary keys: ";
        for (auto& attr_name : create_table_node->primary_keys)
        {
            std::cout << attr_name << " ";
        }
        std::cout << std::endl;
    }
    return ret;
}

int my_parse(const char* sql_str, Sql_Node* sql_node)
{
    int ret = 0;
    YY_BUFFER_STATE buffer = yy_scan_string(sql_str);
    ret = yyparse(sql_node);
    yy_delete_buffer(buffer);
    return ret;
}
